package ca.uqac.dim.turtledb.querytranslator;

import ca.uqac.dim.turtledb.*;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * User: damien
 * Date: 12-11-24
 * Time: 21:22
 */
class FromStatementParser {


    public static final String SQL_FROM_STATEMENT_JOIN = "JOIN";
    public static final String SQL_FROM_STATEMENT_PRODUCT = ",";
    public static final String SQL_FROM_STATEMENT_ON = "ON";
    private List<String> cleaned = new ArrayList<String>();
    private Relation currentLeft = null;
    private List<String> rawSplittedStatement;
    private int processed = 0;

    public FromStatementParser(List<String> splittedFromStatement) {
        rawSplittedStatement = splittedFromStatement;
    }

    public Relation process() {
        for (String s : rawSplittedStatement) {
            //On nettoie d'abord vis à vis des ( et des )
            cleaned.addAll(clean(s));
        }
        while (processed < cleaned.size()) {
            Iteration iteration = new Iteration(currentLeft, cleaned.subList(processed, cleaned.size()));
            currentLeft = iteration.process();
            processed += iteration.getIterationProcessed();
        }
        return currentLeft;

    }

    private List<String> clean(String s) {
        List<String> returnValue = new LinkedList<String>();
        int i;
        if ((i = s.indexOf("(")) != -1) {
            returnValue.addAll(clean(s.substring(0, i)));
            returnValue.add("(");
            returnValue.addAll(clean(s.substring(i + 1)));
        } else if ((i = s.indexOf(")")) != -1) {
            returnValue.addAll(clean(s.substring(0, i)));
            returnValue.add(")");
            returnValue.addAll(clean(s.substring(i + 1)));
        } else if ((i = s.indexOf(",")) != -1) {
            returnValue.addAll(clean(s.substring(0, i)));
            returnValue.add(",");
            returnValue.addAll(clean(s.substring(i + 1)));
        } else {
            if (!s.isEmpty())
                returnValue.add(s);
        }
        return returnValue;
    }

    private static class SingleIteration extends Iteration {

        private List<String> iterationProcessingSplittedStatement;
        private int iterationProcessed = 0;

        public SingleIteration(List<String> strings) {
            super(null, strings);
            iterationProcessingSplittedStatement = strings;
        }

        public Relation process() {//Cas Particulier : Parenthèse
            if (iterationProcessingSplittedStatement.get(0).equals("(")) {
                //Trouver les bornes de la parenthèse
                int compteurParenthese = 1;
                int i = 1;        //Le 0 c'est une ( on le sait !
                while (compteurParenthese >= 1 && i < iterationProcessingSplittedStatement.size()) {
                    if (iterationProcessingSplittedStatement.get(i).equals("(")) compteurParenthese++;
                    if (iterationProcessingSplittedStatement.get(i).equals(")")) compteurParenthese--;
                    i++;
                }

                int finDuPremierOperande = i;
                //Si l'on est sorti de la boucle a cause d'une parenthèse fermée, alors le curseur est allé trop loin;
                if (finDuPremierOperande >= iterationProcessingSplittedStatement.size() || !iterationProcessingSplittedStatement.get(finDuPremierOperande).equals(")")) {
                    finDuPremierOperande--;
                }

                //Verifier que la parenthèse est bien fermée
                if (compteurParenthese != 0) {
                    throw new NotImplementedException();
                }
                //Alors c'est une fin d'iteration et on enleve les parenthèses et on fait la dernière iteration
                Relation relation = new Iteration(null, iterationProcessingSplittedStatement.subList(1, finDuPremierOperande)).process();
                iterationProcessed = finDuPremierOperande + 1;
                return relation;
            }

            //Cas classique
            else {
                iterationProcessed = 1;
                return new VariableTable(iterationProcessingSplittedStatement.get(0));
            }
        }

        public int getIterationProcessed() {
            return iterationProcessed;
        }
    }

    private static class Iteration {

        private Relation iterationLeft;
        private List<String> iterationProcessingSplittedStatement;
        private int iterationProcessed = 0;

        public Iteration(Relation currentLeft, List<String> strings) {
            iterationLeft = currentLeft;
            iterationProcessingSplittedStatement = strings;
        }

        public Relation process() {
            //Cas Particulier : Parenthèse
            if (iterationProcessingSplittedStatement.get(0).equals("(")) {
                //Trouver les bornes de la parenthèse
                int compteurParenthese = 1;
                int i = 1;        //Le 0 c'est une ( on le sait !
                while (compteurParenthese >= 1 && i < iterationProcessingSplittedStatement.size()) {
                    if (iterationProcessingSplittedStatement.get(i).equals("(")) compteurParenthese++;
                    if (iterationProcessingSplittedStatement.get(i).equals(")")) compteurParenthese--;
                    i++;
                }

                int finDuPremierOperande = i;
                //Si l'on est sorti de la boucle a cause d'une parenthèse fermée, alors le curseur est allé trop loin;
                if (finDuPremierOperande >= iterationProcessingSplittedStatement.size() || !iterationProcessingSplittedStatement.get(finDuPremierOperande).equals(")")) {
                    finDuPremierOperande--;
                }

                //Verifier que la parenthèse est bien fermée
                if (compteurParenthese != 0) {
                    throw new NotImplementedException();
                }
                //Cas limite, dernier operande
                if (i >= iterationProcessingSplittedStatement.size()) {
                    //Alors c'est une fin d'iteration et on enleve les parenthèses et on fait la dernière iteration
                    Relation relation = new Iteration(null, iterationProcessingSplittedStatement.subList(1, finDuPremierOperande)).process();
                    iterationProcessed = finDuPremierOperande + 1;
                    return relation;
                }
                //Cas classique
                else if (compteurParenthese == 0
                        && i < iterationProcessingSplittedStatement.size()
                        && (
                        iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(SQL_FROM_STATEMENT_JOIN)
                                || iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(SQL_FROM_STATEMENT_PRODUCT))
                        ) {

                    return recursionAlternate(i, finDuPremierOperande);
                }
                //Cas inconnu
                else {
                    throw new NotImplementedException();
                }
            }
            //Cas classique
            else {
                //Cas classique c'est un JOIN ou un Produit

                //Cas Limite, il n'y a plus d'opérateur, c'est la fin !
                if (iterationProcessingSplittedStatement.size() == 1) {
                    iterationProcessed = 1;
                    return new VariableTable(iterationProcessingSplittedStatement.get(0));
                } else {
                    int indexOfNextOperator = findIndexOfNextOperator();

                    if (indexOfNextOperator == 1) {
                        if (iterationLeft != null) {
                            throw new NotImplementedException();
                        } else {
                            iterationLeft = new VariableTable(iterationProcessingSplittedStatement.get(0));    //On va quand meme pas faire une récursion à gauche ?
                        }

                    }
                    return recursion(indexOfNextOperator);
                }
            }
        }

        private Relation recursionAlternate(int i, int finDuPremierOperande) {
            BinaryRelation relation = findRelationType(i);
            Iteration nextIteration = makeNextIterationAlternate(finDuPremierOperande, relation);


            relation.addOperand(new Iteration(null, iterationProcessingSplittedStatement.subList(1, finDuPremierOperande)).process());
            relation.addOperand(nextIteration.process());
            iterationProcessed = finDuPremierOperande + 2 + nextIteration.getIterationProcessed();


            if (relation instanceof Join) {
                List<String> conditionSplittedString = findConditionSplittedString(iterationProcessed);
                ((Join) relation).setCondition(new ConditionStatementParser(conditionSplittedString).process());
                iterationProcessed += conditionSplittedString.size() > 0 ? conditionSplittedString.size() + 1 : 0;
            }

            return relation;
        }

        private Relation recursion(int indexOfNextOperator) {
            BinaryRelation relation = findRelationType(indexOfNextOperator);
            Iteration nextIteration = makeNextIteration(indexOfNextOperator, relation);

            relation.addOperand(iterationLeft);
            relation.addOperand(nextIteration.process());

            iterationProcessed = indexOfNextOperator + nextIteration.getIterationProcessed() + 1;

            if (relation instanceof Join) {
                List<String> conditionSplittedString = findConditionSplittedString(iterationProcessed);
                ((Join) relation).setCondition(new ConditionStatementParser(conditionSplittedString).process());
                iterationProcessed += conditionSplittedString.size() + 1;
            }

            return relation;
        }

        private Iteration makeNextIterationAlternate(int finDuPremierOperande, NAryRelation relation) {
            //Cas 1 c'est un JOIN
            Iteration nextIteration;
            if (relation instanceof Join) {
                nextIteration = new SingleIteration(iterationProcessingSplittedStatement.subList(finDuPremierOperande + 2, iterationProcessingSplittedStatement.size()));
            }
            //Cas2 C'est un X
            else if (relation instanceof Product) {
                nextIteration = new Iteration(null, iterationProcessingSplittedStatement.subList(finDuPremierOperande + 2, iterationProcessingSplittedStatement.size()));
            } else {
                throw new NotImplementedException();
            }
            return nextIteration;
        }

        private Iteration makeNextIteration(int indexOfNextOperator, NAryRelation relation) {
            Iteration nextIteration;
            //Cas 1 c'est un JOIN
            if (relation instanceof Join) {
                nextIteration = new SingleIteration(iterationProcessingSplittedStatement.subList(indexOfNextOperator + 1, iterationProcessingSplittedStatement.size()));
            } else if (relation instanceof Product) {
                //Cas2 C'est un X
                nextIteration = new Iteration(null, iterationProcessingSplittedStatement.subList(indexOfNextOperator + 1, iterationProcessingSplittedStatement.size()));
            } else {
                throw new NotImplementedException();
            }
            return nextIteration;
        }

        private int findIndexOfNextOperator() {
            int indexOfNextOperator = 0;
            while (indexOfNextOperator < iterationProcessingSplittedStatement.size()
                    && !iterationProcessingSplittedStatement.get(indexOfNextOperator).equalsIgnoreCase(SQL_FROM_STATEMENT_JOIN)
                    && !iterationProcessingSplittedStatement.get(indexOfNextOperator).equalsIgnoreCase(SQL_FROM_STATEMENT_PRODUCT)) {
                indexOfNextOperator++;
            }
            return indexOfNextOperator;
        }

        private BinaryRelation findRelationType(int indexOfOperator) {
            BinaryRelation relation;//On regarde le type d'operateur
            if (iterationProcessingSplittedStatement.get(indexOfOperator).equalsIgnoreCase(SQL_FROM_STATEMENT_JOIN)) {
                relation = new Join();
            } else if (iterationProcessingSplittedStatement.get(indexOfOperator).equalsIgnoreCase(SQL_FROM_STATEMENT_PRODUCT)) {
                relation = new Product();
            } else {
                throw new NotImplementedException();
            }
            return relation;
        }

        private List<String> findConditionSplittedString(int endOfRelation) {
            int i = endOfRelation;
            int debutCondition = -1;
            int finCondition;
            int compteurParenthèse = 0;
            while (i < iterationProcessingSplittedStatement.size()
                    &&
                    (compteurParenthèse > 0
                            ||
                            !(iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(SQL_FROM_STATEMENT_JOIN)
                                    ||
                                    iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(SQL_FROM_STATEMENT_PRODUCT))
                    )) {
                if (iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(SQL_FROM_STATEMENT_ON))
                    debutCondition = i + 1;
                if (iterationProcessingSplittedStatement.get(i).equalsIgnoreCase("(")) compteurParenthèse++;
                if (iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(")")) compteurParenthèse--;
                i++;
            }
            finCondition = i;
            if (debutCondition != -1) {
                return iterationProcessingSplittedStatement.subList(debutCondition, finCondition);
            } else {
                return new ArrayList<String>(0);
            }
        }

        public int getIterationProcessed() {
            return iterationProcessed;
        }


    }
}
